/*
 *  Copright: 2008 Philipp Leitner, Distributed Systems Group,
 *            TU Wien, leitner@infosys.tuwien.ac.at
 *
 *  This file is part of DAIOS.
 *
 *  DAIOS is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation.
 * 
 *  DAIOS is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with DAIOS.  If not, see
 *  <http://www.gnu.org/licenses/>.
 * 
 */

package at.ac.tuwien.infosys.dsg.daios.wsdl;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Date;
import java.util.Hashtable;

import at.ac.tuwien.infosys.dsg.daios.exceptions.UnsupportedClassException;
import at.ac.tuwien.infosys.dsg.daios.util.EncodingHelper;

/**
 * This class holds the mappings of XSD data types to
 * Java types, and is a general utility class for various
 * tasks in the Daios framework.
 * 
 * @author philipp
 *
 */
public class AtomicTypesMapper {
	
	// the mapping table
	private static Hashtable<Class,String> mappings = null;
	// the reverse mapping
	private static Hashtable<String,Class> reverse = null;
	// default values for the java types
	private static Hashtable<Class,Object> defaultVals = null;
	
	// define the mapping table
	// -- for simplicity done on load
	static {
		
		mappings = new Hashtable<Class,String>();
		mappings.put(String.class,"string");
		mappings.put(Object.class,"anyType");
		mappings.put(String[].class,"string[]");
		mappings.put(Object[].class,"anyType[]");
		mappings.put(Integer.class,"int");
		mappings.put(Integer[].class,"int[]");
		mappings.put(Short.class,"short");
		mappings.put(Short[].class,"short[]");
		mappings.put(Long.class,"long");
		mappings.put(Long[].class,"long[]");
		mappings.put(Boolean.class,"boolean");
		mappings.put(Boolean[].class,"boolean[]");
		mappings.put(Float.class,"float");
		mappings.put(Float[].class,"float[]");
		mappings.put(Double.class,"double");
		mappings.put(Double[].class,"double[]");
		mappings.put(Byte[].class, "base64Binary");
		mappings.put(Date.class, "dateTime");
		mappings.put(Date[].class, "dateTime[]");
		
		reverse = new Hashtable<String,Class>();
		reverse.put("string", String.class);
		reverse.put("anyType", Object.class);
		reverse.put("anyType[]", Object[].class);
		reverse.put("string[]", String[].class);
		reverse.put("int", Integer.class);
		reverse.put("int[]", Integer[].class);
		reverse.put("short", Short.class);
		reverse.put("short[]", Short[].class);
		reverse.put("long", Long.class);
		reverse.put("long[]", Long[].class);
		reverse.put("boolean", Boolean.class);
		reverse.put("boolean[]", Boolean[].class);
		reverse.put("float", Float.class);
		reverse.put("float[]", Float[].class);
		reverse.put("double", Double.class);
		reverse.put("double[]", Double[].class);
		reverse.put("base64Binary", Byte[].class);
		reverse.put("dateTime", Date.class);
		reverse.put("dateTime[]", Date[].class);
		
		defaultVals = new Hashtable<Class,Object>();
		defaultVals.put(String.class,"");
		defaultVals.put(Object.class, new Object());
		defaultVals.put(Integer.class, new Integer(0));
		defaultVals.put(Short.class, new Short((short)0));
		defaultVals.put(Long.class, new Long(0));
		defaultVals.put(Byte.class, new Byte("0"));
		defaultVals.put(Boolean.class, new Boolean(false));
		defaultVals.put(Float.class, new Float(0.0f));
		defaultVals.put(Double.class, new Double(0.0d));
		defaultVals.put(Byte[].class, new Byte[0]);
		defaultVals.put(Date.class, new Date());
	}
	
	/**
	 * Get the XSD type to a concrete Java type.
	 * 
	 * @param c The Java type.
	 * @return The XSD type name as String.
	 */
	public static String get(Class c) {
		return mappings.get(c);
	}
	
	/**
	 * Get that Java type to an XSD type.
	 * 
	 * @param s The XSD type as string
	 * (either only the local name or full qualified).
	 * @return The Java representation of this XSD type.
	 */
	public static Class get(String s) {
		
		// remove the prefix
		s = s.replaceFirst(".*:","");
		
		return reverse.get(s);

	}

	/**
	 * Get that Java array type to a (simple) XSD type.
	 * Example: getArray("string") will return a class
	 * representing java.lang.String[] .
	 * 
	 * @param s The XSD type as string
	 * (either only the local name or full qualified).
	 * @return The Java representation of an array of this XSD type.
	 */
	public static Class getArray(String s) {
		
		// remove the prefix
		s = s.replaceFirst(".*:","");
		
		return reverse.get(s+"[]");
		
	}
	
	/**
	 * Get the (non-array) XSD type to a concrete Java array type.
	 * Example: getArray(java.lang.String[].class) will yield a
	 * result of "string" .
	 * 
	 * @param c The Java array type.
	 * @return The XSD type name as String.
	 */	
	public static String getArray(Class c) {
		
		String s = get(c);
		
		// cut off the '[]' at the end
		if(s.endsWith("[]"))
			return s.substring(0, s.length()-2);
		else
			return s;
	}
	
	/**
	 * Get the default object to a given Java class.
	 * Example: the default value for java.lang.String
	 * is the empty String ("").
	 * 
	 * @param c The Java class.
	 * @return The default value for this Java class.
	 */
	public static Object getDefault(Class c) {
		return defaultVals.get(c);
	}
	
	/**
	 * Get the default value to a given Java array class.
	 * Example: the default value for java.lang.String[]
	 * is the array returned by 'new String[0]', i.e.
	 * a String array of size 0.
	 * 
	 * @param c The Java array clas.
	 * @return The default value for this class.
	 */
	public static Object getArrayDefault(Class c) {
		return Array.newInstance(c,0);
	}
	
	/**
	 * Check if the mapping tables contain a certain mapping.
	 * 
	 * @param mapping The mapping to check.
	 * @return True if there is such a mapping in the mapping
	 * tables, false otherwise.
	 */
	public static boolean contains(String mapping) {
		return (reverse.get(mapping) != null);
	}
	
	/**
	 * Construct a new object with a given type from a String.
	 * This method can be seen as utility which converts
	 * Strings to a number of supported formats. If an
	 * unsupported conversion is requested a 
	 * <link>at.ac.tuwien.infosys.dsg.daios.exceptions.UnsupportedClassException</link>
	 * is thrown.
	 * 
	 * @param stringVal The String value to convert.
	 * @param targetClass The target clas of the conversion.
	 * @return The converted value, which can be casted to the target class.
	 * 
	 * @throws UnsupportedClassException
	 * @throws IOException
	 */
	@SuppressWarnings("deprecation")
	public static Object construct(String stringVal, Class targetClass)
		throws UnsupportedClassException, IOException {
		
		if(targetClass.equals(String.class))
			return stringVal;
		if(targetClass.equals(Object.class))
			return stringVal;
		if(targetClass.equals(Integer.class))
			return Integer.parseInt(stringVal);
		if(targetClass.equals(Short.class))
			return Short.parseShort(stringVal);
		if(targetClass.equals(Long.class))
			return Long.parseLong(stringVal);
		if(targetClass.equals(Byte.class))
			return Byte.parseByte(stringVal);
		if(targetClass.equals(Boolean.class))
			return Boolean.parseBoolean(stringVal);		
		if(targetClass.equals(Float.class))
			return Float.parseFloat(stringVal);
		if(targetClass.equals(Double.class))
			return Double.parseDouble(stringVal);	
		if(targetClass.equals(Date.class))
			return new Date(stringVal);
		if(targetClass.equals(Byte[].class))
			return EncodingHelper.getInstance().base64Decode(stringVal);
		
		throw new UnsupportedClassException(targetClass);
		
	}
	
	/**
	 * Convert any simple, non-array object to a String.
	 * Usually this conversion will happen simply by calling
	 * the object's toString() method, but exceptions (as
	 * the case with Byte[]) are possible.
	 * 
	 * @param o The object to convert to String.
	 * @return The converted object.
	 */
	public static String encodeForWire(Object o) {
		
		// special case: byte[]
		if(o instanceof Byte[])
			return EncodingHelper.getInstance().base64Encode((Byte[])o);
		
		// any other case
		else
			return o.toString();
		
	}

	/**
	 * Get the default type. This method should be used to query the default
	 * type for conversions if no type information is given.
	 * 
	 * @return The default type.
	 */
	public static Class getDefaultClass() {
	
		return String.class;
		
	}

	/**
	 * Get the default array type. This method should be used to query the default
	 * type for conversions if no type information for an array is given.
	 * 
	 * @return The default array type.
	 */
	public static Class getDefaultArrayClass() {
	
		return String[].class;

	}
	
}
